﻿Namespace Imaging
    Public Class GDIBitmap
        Implements IDisposable

        Private _PixelBuffer() As Byte
        Private _BufferSize As Integer
        Private _PixelSize As Byte
        Private _bmp As Drawing.Bitmap
        Private _bmpData As Drawing.Imaging.BitmapData

        Sub New(ByVal bmp As Drawing.Bitmap)
            LockBitmap(bmp)
        End Sub

        Public Sub LockBitmap()
            LockBitmap(_bmp)
        End Sub

        Public Sub LockBitmap(ByVal bmp As Drawing.Bitmap)
            If _bmpData IsNot Nothing Then _bmp.UnlockBits(_bmpData)
            _bmp = bmp

            _bmpData = _bmp.LockBits(New Drawing.Rectangle(0, 0, _bmp.Width, _bmp.Height), _
                                     Drawing.Imaging.ImageLockMode.ReadWrite, _bmp.PixelFormat)
            _BufferSize = _bmpData.Stride * _bmpData.Height

            Const BPPMask = &HFF00
            If ((_bmpData.PixelFormat And BPPMask) = &H4000) Then
                _PixelSize = 8
            ElseIf ((_bmpData.PixelFormat And BPPMask) = &H2800) Then
                _PixelSize = 6
            ElseIf ((_bmpData.PixelFormat And BPPMask) = &H2000) Then
                _PixelSize = 4
            ElseIf ((_bmpData.PixelFormat And BPPMask) = &H1800) Then
                _PixelSize = 3
            ElseIf ((_bmpData.PixelFormat And BPPMask) = &H1000) Then
                _PixelSize = 2
            Else
                _PixelSize = 1
            End If

            If (_PixelSize < 3) OrElse (_PixelSize > 4) Then
                Throw New InvalidCastException(String.Format("The bitdepth of {0} ist invalid. Bitdepth of 24/32 expected.", _PixelSize * 8))
            End If

            ReDim _PixelBuffer(_BufferSize)

            System.Runtime.InteropServices.Marshal.Copy(_bmpData.Scan0, _PixelBuffer, 0, _BufferSize)
        End Sub

        Public ReadOnly Property BitDepth() As Byte
            Get
                Return _PixelSize * 8
            End Get
        End Property

        Public ReadOnly Property PixelSize() As Byte
            Get
                Return _PixelSize
            End Get
        End Property

        Public ReadOnly Property PixelFormat() As System.Drawing.Imaging.PixelFormat
            Get
                Return _bmpData.PixelFormat
            End Get
        End Property

        Public Property Pixel(ByVal x As Integer, ByVal y As Integer) As Drawing.Color
            Get
                Dim Ofs As Integer = ((y * _bmpData.Stride) + (x * _PixelSize))
                Return Drawing.Color.FromArgb(_PixelBuffer(Ofs + 2), _PixelBuffer(Ofs + 1), _PixelBuffer(Ofs + 0))
            End Get
            Set(ByVal value As Drawing.Color)
                Dim Ofs As Integer = ((y * _bmpData.Stride) + (x * _PixelSize))
                _PixelBuffer(Ofs + 2) = value.R
                _PixelBuffer(Ofs + 1) = value.G
                _PixelBuffer(Ofs + 0) = value.B
            End Set
        End Property

        Public Property Pixel2(ByVal x As Integer, ByVal y As Integer) As Drawing.Color
            Get
                Dim Ofs As Integer = ((Math.Min(Math.Max(y, 0), _bmpData.Height - 1) * _bmpData.Stride) + (Math.Min(Math.Max(x, 0), _bmpData.Width - 1) * _PixelSize))
                Return Drawing.Color.FromArgb(_PixelBuffer(Ofs + 2), _PixelBuffer(Ofs + 1), _PixelBuffer(Ofs + 0))
            End Get
            Set(ByVal value As Drawing.Color)
                Dim Ofs As Integer = ((Math.Min(Math.Max(y, 0), _bmpData.Height - 1) * _bmpData.Stride) + (Math.Min(Math.Max(x, 0), _bmpData.Width - 1) * _PixelSize))
                _PixelBuffer(Ofs + 2) = value.R
                _PixelBuffer(Ofs + 1) = value.G
                _PixelBuffer(Ofs + 0) = value.B
            End Set
        End Property

        Public ReadOnly Property RawData() As Byte()
            Get
                Return _PixelBuffer
            End Get
        End Property

        Public Property RawData(ByVal Ofs As Integer) As Byte
            Get
                Return _PixelBuffer(Ofs)
            End Get
            Set(ByVal value As Byte)
                _PixelBuffer(Ofs) = value
            End Set
        End Property

        Public ReadOnly Property Width() As Integer
            Get
                Return _bmpData.Width
            End Get
        End Property

        Public ReadOnly Property Height() As Integer
            Get
                Return _bmpData.Height
            End Get
        End Property

        Public ReadOnly Property IsLocked() As Boolean
            Get
                Return (_bmpData IsNot Nothing)
            End Get
        End Property

        Public Function UnLockBitmap() As Drawing.Bitmap

            System.Runtime.InteropServices.Marshal.Copy(_PixelBuffer, 0, _bmpData.Scan0, _BufferSize)
            _bmp.UnlockBits(_bmpData)
            _bmpData = Nothing
            Return _bmp
        End Function

#Region "IDisposable"

        Private disposedValue As Boolean = False        ' To detect redundant calls

        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not Me.disposedValue Then
                If disposing Then
                    If _bmpData IsNot Nothing Then _bmp.UnlockBits(_bmpData)
                    _bmpData = Nothing
                End If
            End If
            Me.disposedValue = True
        End Sub

        ' This code added by Visual Basic to correctly implement the disposable pattern.
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region

    End Class
End Namespace

